# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys

import svn.core
import svn.ra

import gvn.cmdline
import gvn.errors
import gvn.wc


helptext__gvn_upgrade = """\
upgrade: Upgrade a working copy and associated changebranches.
"""


class _FakeCommitInfo(object):
  """Fake svn_commit_info_t for gvn.wc.ChangeState.Snapshot."""
  def __init__(self, path, ra, head, pool):
    """Stat path to fill in revision and date fields.

    Arguments:
    path -- root-relative path to stat
    ra   -- svn_ra_session_t
    head -- head revision
    pool -- scratch pool
    """
    dirent = svn.ra.stat(ra, path, head, pool)
    self.revision = dirent.created_rev
    self.date = svn.core.svn_time_to_cstring(dirent.time, pool)


def _Upgrade(ctx, wc, old_format):
  """Upgrade wc and associated changebranchs in old_format.

  Arguments:
  ctx           -- gvn.cmdline.Context
  wc            -- gvn.wc.WorkingCopy
  old_format    -- pre-0.2 gvn.wc.ChangeState dict
  """
  # gvn.cmdline._GetWC didn't get a chance to set these due to the OldFormat
  # error in wc.Load.
  ctx._project = wc.project
  ctx._project_config = wc.project_config
  ctx._SetRaCallbacks()

  wc.Open(write_lock=False)
  iterpool = svn.core.Pool(ctx.pool)

  # Build new wc.change_state dict from old_format.
  base_revision = wc.Entry(pool=ctx.pool).revision
  for (path, path_state) in old_format.iteritems():
    iterpool.clear()
    try:
      wc_state = wc.change_state[path_state.change_name]
    except gvn.errors.NoChangeBranchInWC:
      wc_state = gvn.wc.ChangeState(base_revision)
      wc.change_state[path_state.change_name] = wc_state
    entry = wc.Entry(path=path, pool=iterpool)
    wc_state.paths[path] = gvn.wc.PathChangeState(
      path_state.change_name, path_state.kind, entry.revision,
      entry.copyfrom_url, entry.copyfrom_rev,
      path_state.has_prop_mods, path_state.text_status,
      path_state.prop_status, path_state.checksum,
      path_state.mtime, path_state.size)

  # Add repo state file for each changebranch and update the local state to
  # use that revision as the snapshot revision.
  head = wc.project.repository.GetHead(ctx.pool)
  root_url = wc.project.repository.URL()
  base_path = wc.URL()[len(root_url) + 1:]
  for (change_name, wc_state) in wc.change_state.iteritems():
    iterpool.clear()
    cb = gvn.changebranch.ChangeBranch(ctx.config, wc.project, change_name,
                                       wc.project.repository.username)
    sys.stdout.write('upgrade %s... ' % (cb.FullName(),))
    sys.stdout.flush()
    revprops = {'svn:log': cb.description.encode('utf-8')}
    repo_paths = [cb.StatePath().encode('utf8')]
    repo_state = gvn.changebranch.State.NewFromWCState(base_path, wc_state,
                                                       wc, root_url)
    def action(unused_path, unused_pool):
      return gvn.changebranch._AddStateFile(repo_state)
    try:
      commit_info = gvn.commit.Drive(wc.project.repository, revprops,
                                     head, repo_paths, action, pool=iterpool)
    except svn.core.SubversionException, e:
      if e.apr_err not in [svn.core.SVN_ERR_FS_ALREADY_EXISTS,
                           # stupid svndav
                           svn.core.SVN_ERR_RA_DAV_ALREADY_EXISTS]:
        # Something is wrong.
        raise
      # The state file already exists; this cb is already upgraded.  Fake out
      # a svn_commit_info_t for wc_state.Snapshot and inform the user.
      commit_info = _FakeCommitInfo(repo_paths[0], wc.project.repository.ra,
                                    head, iterpool)
      sys.stdout.write('already upgraded!\n')
    else:
      # Upgrade successful; inform the user of new snapshot.
      sys.stdout.write('=> %s@%d\n' % (cb.LatestName(), commit_info.revision))
    # Update local state the revision in which we added the state file.
    wc_state.Snapshot(commit_info, iterpool)

  iterpool.destroy()
  wc.Close()
  wc.Save()
  return 0


def Handle_GvnUpgrade(ctx):
  try:
    ctx.wc
  except gvn.errors.OldFormat, e:
    return _Upgrade(ctx, e.wc, e.change_state)
  sys.stdout.write('already upgraded\n')
  # Save in case we found empty old .gvnstate, overwriting it with empty new
  # .gvnstate file.
  ctx.wc.Save()
  return 0

gvn.cmdline.AddCommand('upgrade', Handle_GvnUpgrade, helptext__gvn_upgrade)
